/*  

Cafechess Library: Cover basic chess programming needs, like GUI, Engine integration, 
FEN and PGN parsing.

                 \\|//
                 (o o)
--------------ooO-(_)-Ooo----------------------------------
Copyright (C) 2009  By Gregory A. Prentice Snoqualmie, WA
                    www.cafechess.org
                    cafechess@comcast.net
                      Oooo
--------------oooO----(  )---------------------------------
              (  )    (_/
               \_)

Microsoft Public License (Ms-PL)
               
Revisions:

Author        Date    Description:

*/
using System;
using System.Text;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;


using Cafechess.Imaging;
using Cafechess.Chess.Parsers;
using Cafechess.Chess.Validation;
using Cafechess.Chess.General;
using Cafechess.General.Handle;

namespace Cafechess.Chess.Controls
{
  /// <summary>
  /// Defines the square information which contains our coordinates and
  /// the type of chess piece contained on the square.
  /// </summary>
  public class PieceSquare
  {
    private string coName;
    public string Name
    {
      get{return coName;}
      set{coName = value;}
    }
    private int coIndex;
    public int Index
    {
      get{return coIndex;}
      set{coIndex=value;}
    }
    
    private bool coColor;
    public bool Color
    {
      get{return coColor;}
      set{coColor = value;}
    }    
    private int coSquareIndex;
    public int SquareIndex
    {
      get{return coSquareIndex;}
      set{coSquareIndex=value;}
    }
    public Rectangle Square
    {
      get{ return coSquare; }
      set{ coSquare = value;}
    }
    Rectangle coSquare;   
    public PieceSquare()
    {
    }
  }

  /// <summary>
  /// Defines a drawable chess piece.
  /// </summary>
  public class DrawablePiece : DrawableBitmap
  {
    public Pieces Piece
    {
      get{return coPiece;}
      set{coPiece=value;}
    }
    Pieces coPiece;

    /// <summary>
    /// Public constructor.
    /// </summary>
    /// <param name="name">algebraic name of the square</param>
    public DrawablePiece(string name) : base(name)
    {
      coPiece = Pieces.None;
    }
    /// <summary>
    /// Removes the image and sets the Piece to None.
    /// </summary>
    public override void Clear()
    {
      base.Clear ();
      Piece = Pieces.None;
    }

  }
	/// <summary>
	/// Summary description for PieceLayer.
	/// </summary>
  public class PieceLayer: Layer, IMouseEvents, IPositionEvents, IValidationUIEvents, IDragDropEvents
  {
    #region Fields
    PieceMoveHelper coMoveHelper;
    private ArrayList coImagePieces;
    private bool coIsFlipped;
    private bool coIsShowPieceDragging;
    Pieces coHidePieces;
    CompassHeading coHideCompass;
    #endregion Fields

    #region Properties
    public bool IsFlipped
    {
      get{return coIsFlipped;}
      set{coIsFlipped = value;}
    }
    public bool IsShowPieceDragging
    {
      get{ return coIsShowPieceDragging;}
      set{ coIsShowPieceDragging = value;}
    }
    
    public System.Windows.Forms.ImageList ChessImages
    {
      set
      {
        ImageList il = (ImageList)value;        
        FreeImages(coImagePieces);
        for(int ndx=0; ndx < il.Images.Count; ndx++)
        {
          coImagePieces.Add(il.Images[ndx]);
        }
        ResetPieces();
        IsRedraw=true;
      }
    }

    public override Rectangle Square
    {
      get
      {
        return base.Square;
      }
      set
      {
        base.Square = value;
      }
    }

    public DrawablePiece SquareClicked
    {
      get{ return coMoveHelper.SquareClicked; }
    }

    public IValidation Validation
    {
      set
      {
        if( coMoveHelper.Validation != null )
          coMoveHelper.Validation.RemoveEvents((IValidationUIEvents)this);
        coMoveHelper.Validation = value;
        if( coMoveHelper.Validation != null )
          coMoveHelper.Validation.AddEvents((IValidationUIEvents)this);
      }
      get
      {
        return coMoveHelper.Validation;
      }
    }
    
    #endregion Properties

    #region Constructors
    /// <summary>
    /// Construct our surface object that will manage the physical drawing bitmap
    /// and the order in which our layers are applied to the image.
    /// </summary>
    /// <param name="rectangle"></param>
    public PieceLayer(ChessSurface cs, Rectangle rectangle, Color background) : base(rectangle,background)
    {
      Parent = cs;
      coHidePieces = Pieces.None;
      coIsShowPieceDragging = true;
      coHideCompass = CompassHeading.None;
      coMoveHelper = new PieceMoveHelper(this);
      coImagePieces = new ArrayList(15);
    }

    #endregion Constructors

    #region Methods
    /// <summary>
    /// Clears our public list of piece images, disposing of them
    /// as we go along.
    /// </summary>
    /// <param name="listImages"></param>
    void FreeImages(ArrayList listImages)
    {
      if(listImages != null )
      {
        for(int ndx=0; ndx < listImages.Count; ndx++)
        {
          ((Bitmap)listImages[ndx]).Dispose();
        }
        listImages.Clear();
      }
    }
    /// <summary>
    /// Re-assign the images to each of our drawing squares as most likely the user
    /// has changed the image of the pieces.
    /// </summary>
    void ResetPieces()
    {
      ChessSurface cs = (ChessSurface)Parent;
      for(int ndx=0; ndx < 64; ndx++)
      {
        PieceSquare ps = cs.FindPiece(ndx);
        if( ps != null )
        {
          DrawablePiece db = Get(ps.Index) as DrawablePiece;
          if(db != null )
          {
            switch( db.Piece )
            {
              case Pieces.None:
              case Pieces.ClosedHand:
              case Pieces.OpenHand:
                break;
              default:
                if( (int)db.Piece <= coImagePieces.Count )
                  db.Drawing = (Bitmap) coImagePieces[(int)db.Piece -1];
                break;
            }
          }
        }
      }
    }
    /// <summary>
    /// Sets the board back to containing no pieces.
    /// </summary>
    void ClearBoard()
    {
      ChessSurface cs = (ChessSurface)Parent;
      for(int ndx=0; ndx < 64; ndx++)
      {
        PieceSquare ps = cs.FindPiece(ndx);
        if( ps != null )
        {
          DrawablePiece db = Get(ps.Index) as DrawablePiece;
          if(db != null )
          {
            db.Clear();
          }
        }
      }
    }
    public void Hide(Pieces pieces, CompassHeading heading)
    {
      coHidePieces = pieces;
      coHideCompass = heading;
      Hide();
    }

    /// <summary>
    /// Used to hide pieces or group of pieces from the display.  They are still available
    /// for the user to move but just not displayed.
    /// </summary>
    /// <param name="pieces"></param>
    /// <param name="heading"></param>
    public void Hide()
    {
//      if(coHidePieces != Pieces.None )
//      {
        // Check each square for a piece.
        foreach(DrawablePiece db in this)
        {
          Hide(db);
        }
        IsRedraw = true;
        Parent.IsRedraw = true;
        RedrawBoard();
//      }
    }

    private void Hide(DrawablePiece db)
    {      
      bool skip = true;
      if( coHideCompass != CompassHeading.None )
      {
        int row = BitboardSquare.Row(db.Name);
        int col = BitboardSquare.Column(db.Name);
        switch(coHideCompass)
        {
          case CompassHeading.NorthWest:
            if( row > 4 && col < 5 )
              skip = false;
            break;
          case CompassHeading.NorthEast:
            if( row > 4 && col > 4 )
              skip = false;
            break;
          case CompassHeading.SouthWest:
            if( row < 5 && col < 5 )
              skip = false;
            break;
          case CompassHeading.SouthEast:
            if( row < 5 && col > 4 )
              skip = false;
            break;
          case CompassHeading.North:
            if( row > 4 )
              skip = false;
            break;
          case CompassHeading.South: 
            if( row < 5 )
              skip = false;
            break;
          case CompassHeading.East: 
            if( col > 4 )
              skip = false;
            break;
          case CompassHeading.West:
            if( col < 5 )
              skip = false;             
            break;
          case CompassHeading.All:
            skip = false;
            break;
          case CompassHeading.SmallCenter:
            if( col > 3 && col < 6 )
            {
              if( row > 3 && row < 6 )
                skip = false;
            }
            break;
          case CompassHeading.BigCenter:
            if( col > 2 && col < 7 )
            {
              if( row > 2 && row < 7 )
                skip = false;
            }
            break;
          case CompassHeading.BiggerCenter:
            if( col > 1 && col < 8 )
            {
              if( row > 1 && row < 8 )
                skip = false;
            }
            break;

        }
        if( skip )
        {
          db.IsEnabled = true;
          return;
        }
      }
      switch(coHidePieces)
      {
        case Pieces.AllKings:
          if(db.Piece == Pieces.WhiteKing || 
            db.Piece == Pieces.BlackKing )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.AllQueens:
          if(db.Piece == Pieces.WhiteQueen || 
            db.Piece == Pieces.BlackQueen )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.AllRooks:
          if(db.Piece == Pieces.WhiteRook || 
            db.Piece == Pieces.BlackRook )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.AllBishops:
          if(db.Piece == Pieces.WhiteBishop || 
            db.Piece == Pieces.BlackBishop )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.AllKnights:
          if(db.Piece == Pieces.WhiteKnight || 
            db.Piece == Pieces.BlackKnight )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.AllPawns:
          if(db.Piece == Pieces.WhitePawn || 
            db.Piece == Pieces.BlackPawn )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.AllNonPawns:
          if(db.Piece != Pieces.WhitePawn && 
            db.Piece != Pieces.BlackPawn )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.AllMinors:
          if(db.Piece == Pieces.WhiteKnight || 
            db.Piece == Pieces.BlackKnight ||
            db.Piece == Pieces.WhiteBishop || 
            db.Piece == Pieces.BlackBishop )
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
        case Pieces.All:
          if( db.Piece != Pieces.None )
            db.IsEnabled = false;
          break;
        case Pieces.None:
          db.IsEnabled = true;
          break;
        default:
          if(db.Piece == coHidePieces)
          {
            db.IsEnabled = false;
          }
          else
          {
            db.IsEnabled = true;
          }
          break;
      }
    }

    /// <summary>
    /// Create a fen string from the current chess piece location on this layer.
    /// </summary>
    /// <returns></returns>
    override public string ToString()
    {
      ChessSurface cs = (ChessSurface)Parent;
      int ndx=0;
      int empty=0;
      StringBuilder fen = new StringBuilder();
      ulong mask=BitboardSquare.MaskHighBit;
      while(mask > 0)
      {
        ndx++;
        string notation = BitboardSquare.MaskToAlpha(mask);
        mask = mask >> 1;
        PieceSquare ps = cs.FindPiece(notation);
        if(ps != null )
        {
          DrawablePiece db = Get(ps.Index) as DrawablePiece;
          switch(db.Piece)
          {
            case Pieces.BlackKing:
            case Pieces.BlackQueen:
            case Pieces.BlackRook:
            case Pieces.BlackBishop:
            case Pieces.BlackKnight:
            case Pieces.BlackPawn:
            case Pieces.WhiteKing:
            case Pieces.WhiteQueen:
            case Pieces.WhiteRook:
            case Pieces.WhiteBishop:
            case Pieces.WhiteKnight:
            case Pieces.WhitePawn:
              if( empty > 0 )
              {
                fen.Append(empty.ToString());
                empty = 0;
              }
            switch(db.Piece)
            {
              case Pieces.BlackKing:
                fen.Append('k');
                break;
              case Pieces.BlackQueen:
                fen.Append('q');
                break;
              case Pieces.BlackRook:
                fen.Append('r');
                break;
              case Pieces.BlackBishop:
                fen.Append('b');
                break;
              case Pieces.BlackKnight:
                fen.Append('n');
                break;
              case Pieces.BlackPawn:
                fen.Append('p');
                break;
              case Pieces.WhiteKing:
                fen.Append('K');
                break;
              case Pieces.WhiteQueen:
                fen.Append('Q');
                break;
              case Pieces.WhiteRook:
                fen.Append('R');
                break;
              case Pieces.WhiteBishop:
                fen.Append('B');
                break;
              case Pieces.WhiteKnight:
                fen.Append('N');
                break;
              case Pieces.WhitePawn:
                fen.Append('P');
                break;
            }
              break;
            default:
              empty++;
              break;
          }
        }
        if( ndx != 64 && ndx%8 == 0)
        {
          if( empty > 0 )
          {
            fen.Append(empty.ToString());
            empty = 0;
          }
          fen.Append('/');
        }
      }
      if( empty > 0 )
      {
        fen.Append(empty.ToString());
      }
      return fen.ToString();
    }

    /// <summary>
    /// Given a x and y coordinate, locate the chess square which
    /// contains the given point.
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    public IDrawable FindSquare(int x,int y)
    {
      IDrawable ireturn = null;
      foreach(IDrawable idraw in this)
      {
        if(idraw.HitTest(new Point(x,y)) )
        {
          ireturn = idraw;
          break;
        }
      }    
      return ireturn;
    }
    
    /// <summary>
    /// This delegate routine gets called by our ChessSurface.GenerateBoard() call.
    /// It is used to initialize our piece square objects.
    /// </summary>
    /// <param name="ps"></param>
    /// <param name="create"></param>
    public void SquareGenerated(PieceSquare ps, bool create)
    {
      DrawableBitmap db;
      if( create )
      {
        db = new DrawablePiece(ps.Name);
        db.IsEnabled = false;
        Add(db);
      }
      else
      {
        db = Get(ps.Index) as DrawableBitmap;
      }
      db.Square = ps.Square;

    }

    override protected void DisposeManaged()
    {
      base.DisposeManaged();
      if( ! IsDisposed )
      {
        base.DisposeManaged();
      }
    }
    override protected void DisposeUnmanaged()
    {
      base.DisposeUnmanaged();
      if( ! IsDisposed )
      {
        FreeImages(coImagePieces);
        base.DisposeUnmanaged();
      }
    }
    /// <summary>
    /// Handles the movement of our piece across the board while it is being dragged.
    /// </summary>
    /// <param name="graphics"></param>
    override public void RenderSpecialized(System.Drawing.Graphics graphics)
    {
      if( coMoveHelper != null )
      {
        if(IsShowPieceDragging)
        {
          coMoveHelper.SourceBitmap.Render(graphics,true);
        }
      }
    }

    #endregion Methods

    #region IMouseEvents Members

    public void EnterEvent(object sender, EventArgs e)
    {
      // TODO:  Add PieceLayer.EnterEvent implementation
    }

    public void LeaveEvent(object sender, EventArgs e)
    {
      // TODO:  Add PieceLayer.LeaveEvent implementation
    }
    public void HoverEvent(object sender, System.EventArgs e)
    {
    }
   
    public void DownEvent(object sender, MouseEventArgs e)
    {
      if(coMoveHelper != null)
        coMoveHelper.DownEvent(sender,e);
    }
    

    public void UpEvent(object sender, MouseEventArgs e)
    {
      if(coMoveHelper != null)
        coMoveHelper.UpEvent(sender,e);
    }

    public void MoveEvent(object sender, MouseEventArgs e)
    {
      if( coMoveHelper != null )
        coMoveHelper.MoveEvent(sender,e);
    }

    #endregion

    #region IPositionEvents Members

    public void PlacePiece(Pieces piece, int square)
    {
      UpdateBoard(Operation.ADD,BitboardSquare.SquareToAlpha(square),null,piece);
    }
    public void SetSideToMove(bool bColor){}
    public void SetCastling(bool WK, bool WQ, bool BK, bool BQ){}
    public void SetEnpassant(string Enpassant){}
    public void SetHalfMoves(int number){}
    public void SetFullMoves(int number){}
    public void Starting()
    {
      ClearBoard();
    }  
    public void Finished()    
    {
      IsRedraw = true;
      Hide();
    }
    #endregion

    #region IValidationUI Members

    public void UpdateBoard(Operation op, string fromSquare, string toSquare, Pieces piece)
    {
      PieceSquare ps;
      DrawablePiece  db;
      ChessSurface cs = (ChessSurface)Parent;
      switch(op)
      {
        case Operation.ADD:
          ps = cs.FindPiece(fromSquare);
          if( ps != null)
          {
            db = Get(ps.Index) as DrawablePiece;
            if(db != null )
            {
              if( piece == Pieces.None )
              {
                db.Clear();
                db.IsEnabled = false;
              }
              else
              {
                db.Drawing = (Bitmap) coImagePieces[(int)piece-1];          
                db.IsEnabled = true;
              }
              db.Piece=piece;
              Hide(db);
            }
          }
          IsRedraw = true;
          break;
        case Operation.Delete:
          ps = cs.FindPiece(fromSquare);
          if( ps != null)
          {
            db = Get(ps.Index) as DrawablePiece;
            if(db != null )
            {
              db.Clear();
              db.IsRedraw = true;
              //db.Piece = Pieces.None;
              db.IsEnabled = false;
            }
          }
          IsRedraw = true;
          break;
        case Operation.MOVE:
          ps = cs.FindPiece(fromSquare);
          if( ps != null)
          {
            db = Get(ps.Index) as DrawablePiece;
            piece = db.Piece;
            db.Clear();
            db.IsRedraw = true;
          }
          ps = cs.FindPiece(toSquare);
          if(ps != null)
          {
            db = Get(ps.Index) as DrawablePiece;
            if(db!=null)
            {
              db.Piece = piece;
              db.Drawing = (Bitmap) coImagePieces[(int)db.Piece -1];
              db.IsEnabled = true;
              db.IsRedraw = true;
              Hide(db);
            }
          }
          IsRedraw = true;
          break;
        case Operation.None:
          break;
      }
    }
    public void RedrawBoard()
    {
      ChessSurface cs = (ChessSurface)Parent;
      IsRedraw = true;
      cs.IsRedraw = true;
      cs.Render();
      if( cs.Control != null )
      {
        Graphics gc = cs.Control.CreateGraphics();
        cs.RenderSpecialized(gc);
        gc.Dispose();
      }
    }

    #endregion

    #region IDragDropEvents Members

    public void DragDropEvent(object sender, DragEventArgs e)
    {
      string str = (string) e.Data.GetData(typeof(string));      
      if(str!= null )
      {
        ChessSurface cs = (ChessSurface) Parent;
        Point pt = cs.Control.PointToClient(new Point(e.X,e.Y));
        PieceSquare ps = cs.FindPiece(pt.X,pt.Y);
        Pieces piece = Pieces.None;

        if( str.Equals("whiteking") )
        {
          piece = Pieces.WhiteKing;
        }
        else if( str.Equals("whitequeen") )          
        {
          piece = Pieces.WhiteQueen;
        }
        else if( str.Equals("whiterook") )
        {
          piece = Pieces.WhiteRook;
        }
        else if( str.Equals("whitebishop") )
        {
          piece = Pieces.WhiteBishop;
        }
        else if( str.Equals("whiteknight") )
        {
          piece = Pieces.WhiteKnight;
        }
        else if( str.Equals("whitepawn") )
        {
          piece = Pieces.WhitePawn;
        }
        else if( str.Equals("blackking") )
        {
          piece = Pieces.BlackKing;
        }
        else if( str.Equals("blackqueen") )
        {
          piece = Pieces.BlackQueen;
        }
        else if( str.Equals("blackrook") )
        {
          piece = Pieces.BlackRook;
        }
        else if( str.Equals("blackbishop") )
        {
          piece = Pieces.BlackBishop;
        }
        else if( str.Equals("blackknight") )
        {
          piece = Pieces.BlackKnight;
        }
        else if( str.Equals("blackpawn") )
        {
          piece = Pieces.BlackPawn;
        }
        else if( str.Equals("delete") )
        {
          piece = Pieces.None;
        }
        if(ps != null)
        {
          UpdateBoard(Operation.ADD,ps.Name,null,piece);
          cs.IsRedraw = true;
          cs.Control.Invalidate();
        }
      }
    }

    public void DragEnterEvent(object sender, DragEventArgs e)
    {
      object obj = e.Data.GetData(typeof(string));      
      if(obj != null )
        e.Effect = DragDropEffects.Copy;
      else 
        e.Effect = DragDropEffects.None;    
    }

    public void DragLeaveEvent(object sender, EventArgs e)
    {
      // TODO:  Add PieceLayer.DragLeaveEvent implementation
    }

    public void DragOverEvent(object sender, DragEventArgs e)
    {
      // TODO:  Add PieceLayer.DragOverEvent implementation
    }

    #endregion
  }
}
